/*
    libhdvbd - Library for building sophisticated DVB applications
    Copyright (C) 2012 Tony Houghton <h@realh.co.uk>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser general Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser general Public License for more details.

    You should have received a copy of the GNU Lesser general Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <glib.h>

#include <stdlib.h>
#include <string.h>

#include "alloc-lcns.h"
#include "dbcommon.h"
#include "i18n.h"

static gboolean
update_lcn(sqlite3 *db, sqlite3_stmt *insert_stmt, GHashTable *hash,
        int *highest, int service_id, int lcn)
{
    gboolean result = FALSE;

    if (!g_hash_table_lookup(hash, GINT_TO_POINTER(lcn)))
    {
        sqlite3_bind_int(insert_stmt, 1, lcn);
        sqlite3_bind_int(insert_stmt, 2, service_id);
        if (sqlite3_step(insert_stmt) != SQLITE_DONE)
        {
            g_warning(_("Failed to allocate lcn %d to service %d: %s"),
                    lcn, service_id, sqlite3_errmsg(db));
        }
        else
        {
            g_hash_table_insert(hash, GINT_TO_POINTER(lcn),
                    GINT_TO_POINTER(service_id));
            if (lcn > *highest)
                *highest = lcn;
            result = TRUE;
        }
        sqlite3_clear_bindings(insert_stmt);
        sqlite3_reset(insert_stmt);
    }
    return result;
}

/* Note to nationalists: These are in alphabetical order */
typedef enum {
    COUNTRY_ENGLAND,
    COUNTRY_NI,
    COUNTRY_SCOTLAND,
    COUNTRY_WALES
} HdvbCountryCode_;

static char const *freesat_country_name[4] =
        { "England", "NI", "Scotland", "Wales" };


static HdvbCountryCode_
get_country_code_from_region(sqlite3 *db, int region)
{
    HdvbCountryCode_ country;
    sqlite3_stmt *stmt = hdvb_db_prepare_statement(db,
            "SELECT name FROM region_codes WHERE code = ?");

    sqlite3_bind_int(stmt, 1, region);
    if (hdvb_db_step(stmt) != SQLITE_ROW)
    {
        g_warning(_("Unable to get region name from code %d"), region);
        sqlite3_finalize(stmt);
        return COUNTRY_ENGLAND;
    }
    const char *region_name = (const char *) sqlite3_column_text(stmt, 0);
    if (g_str_has_prefix(region_name, "Wales"))
        country = COUNTRY_WALES;
    else if (g_str_has_prefix(region_name, "Northern Ireland"))
        country = COUNTRY_NI;
    else if (g_str_has_prefix(region_name, "Scotland"))
        country = COUNTRY_SCOTLAND;
    else
        country = COUNTRY_ENGLAND;
    sqlite3_finalize(stmt);
    return country;
}

static int
lookup_lcn_by_service_name(sqlite3 *db, const char *service_name)
{
    int service_id = -1;
    sqlite3_stmt *stmt = hdvb_db_prepare_statement(db,
            "SELECT service_id FROM services WHERE service_name = ?");

    sqlite3_bind_text(stmt, 1, service_name, -1, SQLITE_TRANSIENT);
    if (hdvb_db_step(stmt) == SQLITE_ROW)
    {
        service_id = sqlite3_column_int(stmt, 0);
    }
    sqlite3_finalize(stmt);
    return service_id;
}

static int
bind_lcn_by_service_name(sqlite3 *db, sqlite3_stmt *insert_stmt,
        int lcn, const char *service_name, GHashTable *hash, int *highest)
{
    int service_id = lookup_lcn_by_service_name(db, service_name);
    if (service_id != -1)
    {
        update_lcn(db, insert_stmt, hash, highest, service_id, lcn);
    }
    return service_id;
}

/* Some channels lack proper regions for some reason */
static void
freesat_bbc_fudge(sqlite3 *db, sqlite3_stmt *insert_stmt,
        int chan, int region, GHashTable *hash, int *highest)
{
    const char *country;
    char *bbc_name;

    country = freesat_country_name[get_country_code_from_region(db, region)];
    bbc_name = g_strdup_printf("BBC %d %s", chan, country);
    bind_lcn_by_service_name(db, insert_stmt, chan + 100,
            bbc_name, hash, highest);
    g_free(bbc_name);
}

static void
freesat_bbc1hd_fudge(sqlite3 *db, sqlite3_stmt *insert_stmt,
        int region, GHashTable *hash, int *highest)
{
    const char *bbc_name;

    switch (get_country_code_from_region(db, region))
    {
        case COUNTRY_NI:
            bbc_name = "BBC 1 NI HD";
            break;
        case COUNTRY_SCOTLAND:
            bbc_name = "BBC 1 Scot HD";
            break;
        case COUNTRY_WALES:
            bbc_name = "BBC 1 Wales HD";
            break;
        default:
            bbc_name = "BBC ONE HD";
            break;
    }
    bind_lcn_by_service_name(db, insert_stmt, 108, bbc_name, hash, highest);
}

static void
freesat_itv1_fudge(sqlite3 *db, sqlite3_stmt *insert_stmt,
        int region, GHashTable *hash, int *highest)
{
    const char *itv_name;

    switch (get_country_code_from_region(db, region))
    {
        case COUNTRY_NI:
            itv_name = "UTV";
            break;
        case COUNTRY_WALES:
            itv_name = "ITV1 Wales";
            break;
        default:
            itv_name = "ITV1 Central W";
            break;
    }
    bind_lcn_by_service_name(db, insert_stmt, 103, itv_name, hash, highest);
}

static void
freesat_c4_fudge(sqlite3 *db, sqlite3_stmt *insert_stmt,
        int region, GHashTable *hash, int *highest)
{
    int c4_lcn, s4c_lcn;
    int s4c_sid = -1;

    if (get_country_code_from_region(db, region) == COUNTRY_WALES)
    {
        c4_lcn = 120;
        s4c_lcn = 104;
    }
    else
    {
        c4_lcn = 104;
        s4c_lcn = 120;
    }
    if (!g_hash_table_lookup(hash, GINT_TO_POINTER(s4c_lcn)))
    {
        s4c_sid = bind_lcn_by_service_name(db, insert_stmt, s4c_lcn,
                "S4C Digidol", hash, highest);
    }
    if (!g_hash_table_lookup(hash, GINT_TO_POINTER(c4_lcn)))
    {
        /* We need to find a service which uses c4_lcn on region 65535 *only*
         * and which isn't S4C
         */
        int result;

        if (s4c_sid == -1)
            s4c_sid = lookup_lcn_by_service_name(db, "S4C Digidol");
        sqlite3_stmt *serv_stmt = hdvb_db_prepare_statement(db,
                "SELECT service_id FROM lcns WHERE lcn = ? AND region = 65535 "
                "AND service_id != ?");
        sqlite3_stmt *reg_stmt = hdvb_db_prepare_statement(db,
                "SELECT region FROM lcns WHERE service_id = ?");
        sqlite3_bind_int(serv_stmt, 1, c4_lcn);
        sqlite3_bind_int(serv_stmt, 2, s4c_sid);
        while ((result = hdvb_db_step(serv_stmt)) == SQLITE_ROW)
        {
            int service_id = sqlite3_column_int(serv_stmt, 0);
            gboolean wrong_region = FALSE;

            sqlite3_bind_int(reg_stmt, 1, service_id);
            while ((result = hdvb_db_step(reg_stmt)) == SQLITE_ROW)
            {
                if (sqlite3_column_int(reg_stmt, 0) != 65535)
                {
                    wrong_region = TRUE;
                    break;
                }
            }
            sqlite3_reset(reg_stmt);
            if (!wrong_region)
            {
                update_lcn(db, insert_stmt, hash, highest, service_id, c4_lcn);
                break;
            }
        }
        sqlite3_finalize(reg_stmt);
        sqlite3_finalize(serv_stmt);
    }
}

static gboolean
iter_services(sqlite3 *db, sqlite3_stmt *insert_stmt,
        int region, GHashTable *hash, int *highest)
{
    int result;
    sqlite3_stmt *all_servs_stmt = hdvb_db_prepare_statement(db,
            "SELECT service_id FROM services WHERE lcn = 0"
            "   ORDER BY service_name");
    sqlite3_stmt *lcns_stmt = (region == -1)
            ? hdvb_db_prepare_statement(db,
            "SELECT lcn FROM lcns WHERE service_id = ? ORDER BY lcn")
            : hdvb_db_prepare_statement(db,
                "SELECT lcn FROM lcns WHERE service_id = ?"
                "   AND region = ?");
    gboolean failed = FALSE;

    g_return_val_if_fail(all_servs_stmt && lcns_stmt && insert_stmt, TRUE);
    while ((result = hdvb_db_step(all_servs_stmt)) == SQLITE_ROW)
    {
        int service_id = sqlite3_column_int(all_servs_stmt, 0);

        sqlite3_bind_int(lcns_stmt, 1, service_id);
        if (region != -1)
            sqlite3_bind_int(lcns_stmt, 2, region);
        while ((result = hdvb_db_step(lcns_stmt)) == SQLITE_ROW)
        {
            if (update_lcn(db, insert_stmt, hash, highest,
                    service_id, sqlite3_column_int(lcns_stmt, 0)))
            {
                break;
            }
        }
        if (result != SQLITE_ROW)
            failed = TRUE;
        sqlite3_clear_bindings(lcns_stmt);
        sqlite3_reset(lcns_stmt);
    }
    sqlite3_finalize(all_servs_stmt);
    sqlite3_finalize(lcns_stmt);
    return failed;
}

int
hdvb_alloc_lcns(sqlite3 *db, int region)
{
    int highest = 0;
    /* hash keeps track of which LCNs have already been used */
    GHashTable *hash = g_hash_table_new(g_direct_hash, g_direct_equal);
    sqlite3_stmt *insert_stmt = hdvb_db_prepare_statement(db,
            "UPDATE services SET lcn = ? WHERE service_id = ?");

    /* Make one pass trying to use correct region */
    if (iter_services(db, insert_stmt, region, hash, &highest) && region != -1)
    {
        /* If any failed in that pass first do some fudging... */
        if (!g_hash_table_lookup(hash, GINT_TO_POINTER(101)))
        {
            freesat_bbc_fudge(db, insert_stmt, 1, region, hash, &highest);
        }
        if (!g_hash_table_lookup(hash, GINT_TO_POINTER(102)))
        {
            freesat_bbc_fudge(db, insert_stmt, 2, region, hash, &highest);
        }
        if (!g_hash_table_lookup(hash, GINT_TO_POINTER(103)))
        {
            freesat_itv1_fudge(db, insert_stmt, region, hash, &highest);
        }
        if (!g_hash_table_lookup(hash, GINT_TO_POINTER(104)) ||
            !g_hash_table_lookup(hash, GINT_TO_POINTER(120)))
        {
            freesat_c4_fudge(db, insert_stmt, region, hash, &highest);
        }
        if (!g_hash_table_lookup(hash, GINT_TO_POINTER(108)))
        {
            freesat_bbc1hd_fudge(db, insert_stmt, region, hash, &highest);
        }
        /* ... then make another pass allocating any
         * with "universal" region 65535 */
        if (iter_services(db, insert_stmt, 65535, hash, &highest))
        {
            /* If that fails ignore region altogether */
            iter_services(db, insert_stmt, -1, hash, &highest);
        }
    }
    sqlite3_finalize(insert_stmt);
    g_hash_table_unref(hash);
    return highest;
}

int
hdvb_alloc_lcns_leftovers(sqlite3 *db, int start)
{
    int result;
    sqlite3_stmt *servs_stmt = hdvb_db_prepare_statement(db,
            "SELECT service_id FROM services WHERE lcn = 0 "
            "ORDER BY service_name");
    sqlite3_stmt *insert_stmt = hdvb_db_prepare_statement(db,
            "UPDATE services SET lcn = ? WHERE service_id = ?");

    g_return_val_if_fail(servs_stmt && insert_stmt, start);
    while ((result = sqlite3_step(servs_stmt)) == SQLITE_ROW)
    {
        int service_id = sqlite3_column_int(servs_stmt, 0);

        sqlite3_bind_int(insert_stmt, 1, start);
        sqlite3_bind_int(insert_stmt, 2, service_id);
        if (sqlite3_step(insert_stmt) != SQLITE_DONE)
        {
            g_warning(_("Failed to allocate lcn %d to service %d: %s"),
                    start, service_id, sqlite3_errmsg(db));
        }
        else
        {
            ++start;
        }
        sqlite3_clear_bindings(insert_stmt);
        sqlite3_reset(insert_stmt);
    }
    return start;
}
